home *** CD-ROM | disk | FTP | other *** search
/ NeXT Education Software Sampler 1992 Fall / NeXT Education Software Sampler 1992 Fall.iso / Programming / Source / StereoScope-3.2 / Help.m < prev    next >
Encoding:
Text File  |  1992-04-01  |  11.7 KB  |  382 lines

  1. /* 
  2.  * Help.m, a help object to manage and display RTF help files.
  3.  * The help object owns its own nib section "Help.nib" which has a
  4.  * Help panel - with an NXBrowser to display the help topics, and
  5.  * a scrolling text view to display the help files.  The help files are
  6.  * all stored as RTF text files in a directory called Help within the
  7.  * app wrapper.  At init time, the Help object loads the browser with 
  8.  * names of all the files found in the Help directory.  When a name is
  9.  * chosen from the browser, the help object opens a stream on that file
  10.  * and read the rich text into the text object. The help object also
  11.  * responds to request for context-sensitive help, by trying to find an
  12.  * appropriate help file for the view that was moused down in.  See the
  13.  * helpForObject: method for more detailed explanation of that.
  14.  * This object is a useful addition to any program, because all users
  15.  * appreciate help!
  16.  *
  17.  * Author: Julie Zelenski, NeXT Developer Support
  18.  * You may freely copy, distribute and reuse the code in this example.  
  19.  * NeXT disclaims any warranty of any kind, expressed or implied, as to 
  20.  * its fitness for any particular use.
  21.  */
  22.  
  23. #import "Help.h"
  24. #import "SterOptikonApp.h"
  25. #import <appkit/Application.h>
  26. #import <appkit/Button.h>
  27. #import <appkit/Cell.h>
  28. #import <appkit/Matrix.h>
  29. #import <appkit/MenuCell.h>
  30. #import <appkit/NXBrowser.h>
  31. #import <appkit/NXBrowserCell.h>
  32. #import <appkit/ScrollView.h>
  33. #import <appkit/Text.h>
  34. #import <dpsclient/wraps.h>
  35. #import <sys/dir.h> //for getdirentries()
  36. #import <libc.h>     
  37.  
  38.  
  39. @implementation Help:Object
  40.  
  41.  
  42. - init 
  43. /* For newly created help object, loads the nib section with the Help
  44.  * panel which has the topics browser and a scrolling text view for 
  45.  * displaying help files.  Gets the appDirectory from NXApp, finds help 
  46.  * directory in app wrapper.
  47.  */
  48. {
  49.     sprintf(helpDirectory,"%s/%s",[NXApp appDirectory],"Help");
  50.     sprintf(noHelpFile,"%s/%s",helpDirectory,"No Help.rtf");
  51.     helpPanel = [NXApp loadNibSection:"Help.nib" owner:self];
  52.     return self;
  53. }
  54.  
  55. - setHelpBrowser:anObject;
  56. /* Sets the helpBrowser outlet, and calls on the browser to load up.
  57.  */
  58. {
  59.     helpBrowser = anObject;
  60.     [helpBrowser setDelegate:self];
  61.     [helpBrowser loadColumnZero];
  62.     return self;
  63. }
  64.  
  65. /* TARGET/ACTION METHODS */
  66.  
  67. - generalHelp:sender;
  68. /* This is the target/action method for the "Help" menu item.  This method 
  69.  * will show the "general help" file.
  70.  */
  71. {
  72.     [self showHelpFile:"General Help"];
  73.     return self;
  74. }
  75.  
  76. - browserHit:sender
  77. /* This is the target/action method from the help topics browser.  When
  78.  * a help topic is selected, this method will show the help file for that
  79.  * topic.
  80.  */
  81. {   
  82.     [self showHelpFile:[[[sender matrixInColumn:0] selectedCell] stringValue]];
  83.     return self;
  84. }
  85.  
  86.  
  87. - print:sender;
  88. /* This method is called by the Print menu cell in the main menu.  It will 
  89.  * print the current help file.
  90.  */
  91. {
  92.     [[helpScrollView docView] printPSCode:sender];
  93.     return self;
  94. }
  95.  
  96.  
  97. /* HELP METHODS */
  98.  
  99. - helpForWindow:window;
  100. /* If this window is the main menu, it will ask to display the help file
  101.  * for "Main Menu." (I didn't want to call the Main Menu help file "BusyBox 
  102.  * Menu")  Else it gets the help file for <window title> <window name>
  103.  * "Info Panel" or "Document Menu" for example.
  104.  */
  105. {
  106.     char filename[MAXPATHLEN];
  107.  
  108.     if (window == [NXApp mainMenu])
  109.         sprintf(filename,"Main Menu");
  110.     else 
  111.         sprintf(filename,"%s %s",[window title],[window name]);
  112.     [self showHelpFile:filename];
  113.     return self;
  114. }
  115.  
  116. - helpForView:view atPoint:(NXPoint *)aPt;
  117. /* Gives help for the specified view, which was hit with a Control-mouseDown 
  118.  * at aPt.  Gives feedback to the user which view was hit by framing the
  119.  * bounds of the view with a gray rectangle.  This is done with instance
  120.  * drawing and erased after the helpfile is read and displayed.  If the view
  121.  * is a matrix, the method makes the effort to find the particular cell which
  122.  * was hit and to only frame that cell.
  123.  * This method calls on the method helpForObject which will figure out which
  124.  * help file to display.
  125.  */
  126. {  
  127.     int row,column;
  128.     NXRect b;
  129.     NXPoint p;
  130.     id cell = nil;
  131.  
  132.     [view getBounds:&b];
  133.     if ([view isKindOf:[Matrix class]]) {
  134.         p = *aPt;
  135.     [view convertPoint:&p fromView:nil];
  136.     if (cell = [view getRow:&row andCol:&column forPoint:&p])
  137.         [view getCellFrame:&b at:row :column];
  138.     }
  139.     [view lockFocus];
  140.     PSnewinstance();
  141.     PSsetinstance(YES);
  142.     PSsetgray(NX_DKGRAY);
  143.     NXFrameRectWithWidth(&b,1.0);
  144.     [[view window] flushWindow];
  145.     PSsetinstance(NO);
  146.     if (cell) 
  147.         [self helpForObject:cell];
  148.     else if ([[view window] contentView] == view) 
  149.         [self helpForWindow:[view window]];
  150.     else
  151.         [self helpForObject:view];
  152.     PSnewinstance();
  153.     [view unlockFocus];
  154.     return self;
  155. }
  156.  
  157.  
  158. - helpForObject:object;
  159. /* The method tries to cons together a file name that represents help
  160.  * for the given object.  It makes no assumptions about the tags or
  161.  * titles of the views, rather it employs a general strategy.  For many
  162.  * objects, the name is simply used ("ScrollView","TextField"). For Cells,
  163.  * it strips Cell from the name, for our purposes, TextFieldCell is
  164.  * the same as a TextField.  For Buttons (and ButtonCells), it uses icon + name
  165.  * (radio Button, popUp Button).  For MenuCells, it figures out where it is
  166.  * submenu or a item.  Items display help for their parent menu, (using 
  167.  * helpForWindow: method), submenus use title + menu (Format Menu, Find Menu).
  168.  * What is neat about this general scheme is that all views, windows, menu
  169.  * respond to the control-click, not just the ones I created. Bring up the 
  170.  * Print panel and control-click on the Resolution popup, and you will see 
  171.  * the help file for popups!  Control-click on the Save Panel, you get help for 
  172.  * Save Panel!  This is probably not the way that a real application would 
  173.  * implement help.  It happens to work for mine, because I want the same
  174.  * help file to be displayed for every popup list, the same file for every 
  175.  * button.  In your app, different button have various functions, and you 
  176.  * would want to display different help files for different buttons.  You 
  177.  * will probably devise a scheme using unique tags or titles, a more specific
  178.  * way to determine what help file to display.
  179.  */
  180. {
  181.     char filename[MAXPATHLEN];
  182.     char *suffix;
  183.     int len;
  184.  
  185.     sprintf(filename,"%s",[object name]);
  186.     if ([object isKindOf:[Button class]] || [object isKindOf:[Cell class]]) {
  187.      if ([object icon]) 
  188.          sprintf(filename,"%s %s",[object icon],[object name]);
  189.      if ([object isKindOf:[Cell class]]) {
  190.          len = strlen(filename);
  191.          suffix = filename + (len-4)*sizeof(char);
  192.          if (strcmp("Cell",suffix)==0) {
  193.          filename[len-4] = '\0';
  194.         }
  195.     }
  196.     }
  197.     if ([object isKindOf:[MenuCell class]]) {
  198.         if ([object icon] && (strcmp([object icon],"menuArrow")==0))
  199.         sprintf(filename,"%s %s",[object title],"Menu");
  200.     else {
  201.         return [self helpForWindow:[[object controlView] window]];
  202.     }
  203.     }
  204.     [self showHelpFile:filename];
  205.     return self;
  206. }
  207.  
  208. - showHelpFile:(const char*)filename;
  209. /* Tries to open a stream for the specified RTF text file in the Help 
  210.  * directory so the text object can readRichText.  Also selects the
  211.  * filename in the browser of help topics.  If the filename doesn't exist,
  212.  * it will select and display the "no help" file.  It also brings the
  213.  * help panel to the front.
  214.  */
  215. {
  216.    NXStream *stream;
  217.    char helpFile[MAXPATHLEN];
  218.    static NXPoint origin = {0.0,0.0};
  219.  
  220.     if (![self browser:helpBrowser selectCell:filename inColumn:0])
  221.         [self browser:helpBrowser selectCell:"No Help" inColumn:0];
  222.     sprintf(helpFile,"%s/%s.rtf",helpDirectory,filename);
  223.     if ((stream = NXMapFile(helpFile,NX_READONLY)) == NULL)
  224.         stream = NXMapFile(noHelpFile,NX_READONLY);
  225.     if (stream != NULL) {
  226.         [helpPanel disableFlushWindow];
  227.         [[helpScrollView docView] readRichText:stream]; 
  228.     [[helpScrollView docView] scrollPoint:&origin];
  229.     [[helpPanel reenableFlushWindow] flushWindow];
  230.         NXCloseMemory(stream,NX_FREEBUFFER);
  231.     }
  232.     [helpPanel orderFront:self];
  233.     return self;
  234. }
  235.  
  236.  
  237. /* BROWSER DELEGATE METHODS */
  238.  
  239.  
  240. #define CHUNK 127
  241. static char **addFile(const char *file, int length, char **list, int count)
  242. /* Adds the specified filename to the list of filenames.  It allocates 
  243.  * more memory in chunks as needed.
  244.  */
  245. {
  246.     char *suffix;
  247.     
  248.     if (!list) list = (char **)malloc(CHUNK*sizeof(char *));
  249.     if (suffix = rindex(file,'.')) 
  250.         *suffix  = '\0';     /* strip rtf suffix */
  251.     list[count] = (char *)malloc((length+1)*sizeof(char));
  252.     strcpy(list[count], file);
  253.     count++;
  254.     if (!(count% CHUNK)) {
  255.     list = (char **)realloc(list,(((count/CHUNK)+1)*CHUNK)*sizeof(char *));
  256.     }
  257.     list[count] = NULL;
  258.     return list;
  259. }
  260.  
  261. static void freeList(char **list)
  262. /* Frees the array of filenames
  263.  */
  264.  {
  265.     char **strings;
  266.  
  267.     if (list) {
  268.     strings = list;
  269.     while (*strings) free(*strings++);
  270.     free(list);
  271.     }
  272. }
  273.  
  274. static BOOL isOk(const char *s)
  275. /* checks to make sure the filename is not NULL and to verify that it is
  276.  * not a "dot"--hidden file.
  277.  */
  278. {
  279.     return (!s[0] || s[0] == '.') ? NO : YES;
  280. }
  281.  
  282. static int caseInsensitiveCompare(void *arg1, void *arg2)
  283. /* Compares the two arguments without regard for case using strcasecmp().
  284. */
  285. {
  286.     char *string1, *string2;
  287.  
  288.     string1 = *((char **)arg1);
  289.     string2 = *((char **)arg2);
  290.     return strcasecmp(string1,string2);
  291. }
  292.  
  293. static char **fileList;
  294.  
  295. - (int)browser:sender fillMatrix:matrix inColumn:(int)column
  296. /* This delegate method goes out to the help directory and gets a list
  297.  * of all the files in that directory.  It creates a list of file names
  298.  * for the static variable fileList, and will load the filenames into the 
  299.  * browser on demand (lazy loading).
  300.  */
  301. {
  302.     long basep;
  303.     char *buf;
  304.     struct direct *dp;
  305.     char **list = NULL;
  306.     int cc, fd, fileCount = 0;
  307.     char dirbuf[8192];
  308.  
  309.     if ((fd = open(helpDirectory, O_RDONLY, 0644)) > 0) {
  310.     cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
  311.     while (cc) {
  312.         dp = (struct direct *)buf;
  313.         if (isOk(dp->d_name)) {
  314.         list = addFile(dp->d_name, dp->d_namlen, list, fileCount++);
  315.         }
  316.         buf += dp->d_reclen;
  317.         if (buf >= dirbuf + cc) {
  318.         cc = getdirentries(fd, (buf = dirbuf), 8192, &basep);
  319.         }
  320.     }
  321.     close(fd);
  322.     if (list) qsort(list,fileCount,sizeof(char *),caseInsensitiveCompare);
  323.     }
  324.     freeList(fileList);
  325.     fileList = list;
  326.     return fileCount;
  327. }
  328.  
  329. - browser:sender loadCell:cell atRow:(int)row inColumn:(int)column
  330. /* This delegate method loads the cell for a given row.  The stringValue
  331.  * for that row comes from the fileList.
  332.  */
  333. {
  334.     if (fileList) {
  335.     [cell setStringValueNoCopy:fileList[row]];
  336.     [cell setLeaf:YES];
  337.     }
  338.     return self;
  339. }
  340.  
  341.  
  342. - (BOOL)browser:sender selectCell:(const char *)title inColumn:(int)column
  343. /* This delegate method selects the cell with the given title.  If it finds
  344.  * a cell with that title, it verifies that it has a file entry in the 
  345.  * fileList, forces the loading of the cell, selects it (highlights) and
  346.  * scrolls the browser so the cell is visible.  It returns a boolean value
  347.  * which indicates whether the cell was found.
  348.  */
  349. {
  350.     int row;
  351.     id matrix;
  352.  
  353.     if (title) {
  354.     matrix = [sender matrixInColumn:column];
  355.     if (!fileList) return NO;
  356.     for (row = [matrix cellCount]-1; row >= 0; row--) {
  357.         if (fileList[row] && !strcmp(title, fileList[row])) {
  358.         [sender getLoadedCellAtRow:row inColumn:column];
  359.         [matrix selectCellAt:row :0];
  360.         [matrix scrollCellToVisible:row :0];
  361.         return YES;
  362.         }
  363.     }
  364.     }
  365.     return NO;
  366. }
  367.  
  368.  
  369. /* WINDOW DELEGATE METHODS */
  370.  
  371. - windowWillResize:sender toSize:(NXSize *)frameSize;
  372. /* This method constrains the Help Panel to a reasonable minimum size
  373.  * when the user resizes the panel.
  374.  */
  375. {
  376.     frameSize->width = MAX(frameSize->width,400.0);
  377.     frameSize->height = MAX(frameSize->height,350.0);
  378.     return self;
  379. }
  380.  
  381.  
  382. @end